/*
 * this is what the terminal answers to a ESC-Z or csi0c query.
 */
#define VT100ID "\033[?1;2c"
#define VT102ID "\033[?6c"

static void scr_memset(unsigned int d, unsigned char c, unsigned int count)
{
	if (!count || d+count > phcdev.screen_size) return;
	//printk(" scr_memset to %c -  \n", c);
	while (count--) {
		//printk(" %c %d %d %d |", c, d, (d-vc->vc_origin)%vc->vc_cols, (d-vc->vc_origin)/vc->vc_cols);
		++d;
		writeDDRAM(c, d/disp_cols, d%disp_cols);
		//state((d - vc->vc_origin)/vc->vc_cols, (d - vc->vc_origin)%vc->vc_cols) = c;
	}
	//printk("\n");
}

static void scr_memmove(unsigned int d, unsigned int s, const unsigned int nr)
{
	unsigned int count = nr;
	if (!count || d == s || s+count > phcdev.screen_size || d+count > phcdev.screen_size)
		return;
		
	//printk(" scr_memmove: \n");
	if (d < s) {
		while (count--) {
			//printk(" %d=%x %d %d |", d, state[s], d%vc->vc_cols, d/vc->vc_cols);
			writeDDRAM(phcdev.state[s], d/disp_cols, d%disp_cols);
			//state(d/vc->vc_cols, d%vc->vc_cols) = state[s];
			++d;
			++s;
		}
	} else {
		d += count;
		s += count;
		while (count--) {
			--d;
			--s;
			//printk(" %d=%d %d %d |", d, s, d%vc->vc_cols, (d-vc->vc_origin)/vc->vc_cols);
			writeDDRAM(phcdev.state[s], d/disp_cols, d%disp_cols);
			//state(d/vc->vc_cols, d%vc->vc_cols) = state[s];
		}
	}
	//printk("\n");
}

static void scrup(struct phc_vc_data *vc, unsigned int t, unsigned int b, unsigned int nr)
{
	unsigned int d, s;

	if (t+nr >= b)
		nr = b - t - 1;
	if (b > vc->vc_rows || t >= b || nr < 1)
		return;
	d = (unsigned int)(vc->vc_origin + vc->vc_cols * t);
	s = (unsigned int)(vc->vc_origin + vc->vc_cols*(t + nr));
	//printk("scrup %d %d %d %d %d %d %d \n", d, s, b, t, nr, vc->vc_origin, vc->vc_cols);
	scr_memmove(d, s, (b - t - nr)*(vc->vc_cols));
	scr_memset(d + (b - t - nr) * (vc->vc_cols) - 1, vc->vc_video_erase_char, (vc->vc_cols)*nr);
}

static void scrdown(struct phc_vc_data *vc, unsigned int t, unsigned int b, int nr)
{
	unsigned int s;

	if (t+nr >= b)
		nr = b - t - 1;
	if (b > vc->vc_rows || t >= b || nr < 1)
		return;
	s = (unsigned int)(vc->vc_origin + (t*vc->vc_cols));
	//printk("scrdown %d %d %d %d %ld %d \n", s, b, t, nr, vc->vc_origin, vc->vc_cols);
	scr_memmove(s+(nr*vc->vc_cols), s, (b - t - nr)*(vc->vc_cols));
	scr_memset(s, vc->vc_video_erase_char, vc->vc_cols * nr);
}

/*
 * gotoxy() must verify all boundaries, because the arguments
 * might also be negative. If the given position is out of
 * bounds, the cursor is placed at the nearest margin.
 */
static void gotoxy(struct phc_vc_data *vc, int new_x, int new_y)
{
	int min_y, max_y;

	if (new_x < 0)
		vc->vc_x = 0;
	else {
		if (new_x >= vc->vc_cols)
			vc->vc_x = vc->vc_cols - 1;
		else
			vc->vc_x = new_x;
	}

 	if (vc->vc_decom) {
		min_y = vc->vc_top;
		max_y = vc->vc_bottom;
	} else {
		min_y = 0;
		max_y = vc->vc_rows;
	}
	if (new_y < min_y)
		vc->vc_y = min_y;
	else if (new_y >= max_y)
		vc->vc_y = max_y;
	else
		vc->vc_y = new_y;

	vc->vc_need_wrap = 0;
}

/* for absolute user moves, when decom is set */
static void gotoxay(struct phc_vc_data *vc, int new_x, int new_y)
{
	gotoxy(vc, new_x, vc->vc_decom ? (vc->vc_top + new_y) : new_y);
}

static void lf(struct phc_vc_data *vc)
{
    	/* don't scroll if above bottom of scrolling region, or
	 * if below scrolling region
	 */
	//printk("lf %d %d %d %d %d %d \n", vc->vc_y, vc->vc_x, vc->vc_bottom, vc->vc_rows, vc->vc_top);
    	if (vc->vc_y + 1 == vc->vc_bottom) {
		scrup(vc, vc->vc_top, vc->vc_bottom, 1);
	} else if (vc->vc_y < vc->vc_rows - 1) {
	    	vc->vc_y++;
	}
	vc->vc_need_wrap=0;
}

static void ri(struct phc_vc_data *vc)
{
    	/* don't scroll if below top of scrolling region, or
	 * if above scrolling region
	 */
	if (vc->vc_y == vc->vc_top)
		scrdown(vc, vc->vc_top, vc->vc_bottom, 1);
	else if (vc->vc_y > 0) {
		vc->vc_y--;
	}
	vc->vc_need_wrap=0;
}

static inline void cr(struct phc_vc_data *vc)
{
	vc->vc_x = 0;
	vc->vc_need_wrap=0;
}

static inline void bs(struct phc_vc_data *vc)
{
	if (vc->vc_x) {
		vc->vc_x--;
	} else {
		vc->vc_x = vc->vc_cols -1;
		if (vc->vc_y) {
			vc->vc_y--;
		} else {
			vc->vc_y=0;
		}
	}
	vc->vc_need_wrap=0;
}

static inline void del(struct phc_vc_data *vc)
{
	/* ignored */
}

static void csi_J(struct phc_vc_data *vc, int vpar)
{
	switch (vpar) {
		case 0:	/* erase from cursor to end of display */
			scr_memset(vc->vc_y*vc->vc_cols + vc->vc_x, 
				vc->vc_video_erase_char, ((vc->vc_rows-vc->vc_y)*vc->vc_cols) + vc->vc_x);
			break;
		case 1:	/* erase from start display to cursor */
			scr_memset(0, vc->vc_video_erase_char, vc->vc_y*vc->vc_cols + vc->vc_x);
			break;
		case 2: /* erase whole display */
			clear_all_displays();
			break;
		default:
			return;
	}
	vc->vc_need_wrap=0;
}

static void csi_K(struct phc_vc_data *vc, int vpar)
{
	switch (vpar) {
		case 0:	/* erase from cursor to end of line */
			scr_memset(vc->vc_y*vc->vc_cols + vc->vc_x-1, 
				vc->vc_video_erase_char, vc->vc_cols-vc->vc_x);
			break;
		case 1:	/* erase from start of line to cursor */
			scr_memset(vc->vc_y*vc->vc_cols, 
				vc->vc_video_erase_char, vc->vc_x);
			break;
		case 2: /* erase whole line */
			scr_memset(vc->vc_y*vc->vc_cols, 
				vc->vc_video_erase_char, vc->vc_cols);
			break;
		default:
			return;
	}
	vc->vc_need_wrap=0;
}

static void csi_X(struct phc_vc_data *vc, int vpar)
{
	/* erase the following vpar positions */
	int count;
	
	if (!vpar)
		vpar++;
	count = (vpar > vc->vc_cols - vc->vc_x) ? (vc->vc_cols - vc->vc_x) : vpar;

	scr_memset(vc->vc_y*vc->vc_cols+vc->vc_x, vc->vc_video_erase_char, count);
	vc->vc_need_wrap=0;
}

static void default_attr(struct phc_vc_data *vc)
{
}

static void csi_m(struct phc_vc_data *vc)
{
	/* i cant set any colors on my lcds */
}

static void respond_string(const char *p, struct tty_struct *tty)
{
	while (*p) {
		tty_insert_flip_char(tty, *p, 0);
		p++;
	}
	tty_flip_buffer_push(tty);
}

static void cursor_report(struct phc_vc_data *vc, struct tty_struct *tty)
{
	char buf[40];
	sprintf(buf, "\033[%d;%dR", vc->vc_y + (vc->vc_decom ? vc->vc_top + 1 : 1), vc->vc_x + 1);
	respond_string(buf, tty);
}

static inline void status_report(struct tty_struct *tty)
{
	respond_string("\033[0n", tty);	/* Terminal ok */
}

static inline void respond_ID(struct tty_struct * tty)
{
	respond_string(VT102ID, tty);
}

/* console_lock is held */
static void set_mode(struct phc_vc_data *vc, int on_off)
{
	int i;

	for (i = 0; i <= vc->vc_npar; i++)
		if (vc->vc_ques) {
			switch(vc->vc_par[i]) {	/* DEC private modes set/reset */
			case 1:			/* Cursor keys send ^[Ox/^[[x */
				break;
			case 3:	/* 80/132 mode switch unimplemented */
				break;
			case 5: /* Inverted screen on/off */
				break;
			case 6:			/* Origin relative/absolute */
				vc->vc_decom = on_off;
				gotoxay(vc, 0, 0);
				break;
			case 7:	/* Autowrap on/off */
				break;
			case 8:	/* Autorepeat on/off */
				break;
			case 9:
				break;
			case 25:		/* Cursor on/off */
				vc->vc_deccm = on_off;
				break;
			case 1000:
				break;
			}
		} else {
			switch(vc->vc_par[i]) {	/* ANSI modes set/reset */
			case 3:			/* Monitor (display ctrls) */
				vc->vc_disp_ctrl = on_off;
				break;
			case 4:			/* Insert Mode on/off */
				vc->vc_decim = on_off;
				break;
			case 20:		/* Lf, Enter == CrLf/Lf */
				break;
			}
		}
}

/* console_lock is held */
static void setterm_command(struct phc_vc_data *vc)
{
	switch(vc->vc_par[0]) {
		case 1:	/* set color for underline mode */
			break;
		case 2:	/* set color for half intensity mode */
			break;
		case 8:	/* store colors as defaults */
			break;
		case 9:	/* set blanking interval */
			break;
		case 10: /* set bell frequency in Hz */
			break;
		case 11: /* set bell duration in msec */
			break;
		case 12: /* bring specified console to the front */
			break;
		case 13: /* unblank the screen */
			break;
		case 14: /* set vesa powerdown interval */
			break;
		case 15: /* activate the previous console */
			break;
	}
}

/* console_lock is held */
static void csi_at(struct phc_vc_data *vc, unsigned int nr)
{
}

/* console_lock is held */
static void csi_L(struct phc_vc_data *vc, unsigned int nr)
{
	if (nr > vc->vc_rows - vc->vc_y)
		nr = vc->vc_rows - vc->vc_y;
	else if (!nr)
		nr = 1;
	scrdown(vc, vc->vc_y, vc->vc_bottom, nr);
	vc->vc_need_wrap = 0;
}

/* console_lock is held */
static void csi_P(struct phc_vc_data *vc, unsigned int nr)
{
}

/* console_lock is held */
static void csi_M(struct phc_vc_data *vc, unsigned int nr)
{
	if (nr > vc->vc_rows - vc->vc_y)
		nr = vc->vc_rows - vc->vc_y;
	else if (!nr)
		nr=1;
	scrup(vc, vc->vc_y, vc->vc_bottom, nr);
}

/* console_lock is held (except via vc_init->reset_terminal */
static void save_cur(struct phc_vc_data *vc)
{
}

/* console_lock is held */
static void restore_cur(struct phc_vc_data *vc)
{
}

enum { ESnormal, ESesc, ESsquare, ESgetpars, ESgotpars, ESfunckey,
	EShash, ESsetG0, ESsetG1, ESpercent, ESignore, ESnonstd,
	ESpalette };

/* console_lock is held (except via vc_init()) */
static void reset_terminal(struct phc_vc_data *vc, int do_clear)
{
	vc->vc_top		= 0;
	vc->vc_bottom		= disp_rows;
	vc->vc_state		= ESnormal;
	vc->vc_ques		= 0;
	vc->vc_video_erase_char = charmap[' '];
	vc->vc_cols = disp_cols;
	vc->vc_rows = disp_rows;
	vc->vc_origin = 0;
	vc->vc_scr_end = disp_cols;
	vc->vc_screenbuf_size = 0;
	
	vc->vc_disp_ctrl	= 0;
	vc->vc_toggle_meta	= 0;

	vc->vc_decscnm		= 0;
	vc->vc_decom		= 0;
	vc->vc_decawm		= 1;
	vc->vc_deccm		= 0;
	vc->vc_decim		= 0;

	vc->vc_tab_stop[0]	= 0x01010100;
	vc->vc_tab_stop[1]	=
	vc->vc_tab_stop[2]	=
	vc->vc_tab_stop[3]	=
	vc->vc_tab_stop[4]	=
	vc->vc_tab_stop[5]	=
	vc->vc_tab_stop[6]	=
	vc->vc_tab_stop[7]	= 0x01010101;


	default_attr(vc);

	gotoxy(vc, 0, 0);
	if (do_clear)
	    csi_J(vc, 2);
}

/* console_lock is held */
static void do_con_trol(struct tty_struct *tty, struct phc_vc_data *vc, int c)
{
	/*
	 *  Control characters can be used in the _middle_
	 *  of an escape sequence.
	 */
	switch (c) {
	case 0:
		return;
	case 7:
		return;
	case 8:
		bs(vc);
		return;
	case 9:
		while (vc->vc_x < vc->vc_cols - 1) {
			vc->vc_x++;
			if (vc->vc_tab_stop[vc->vc_x >> 5] & (1 << (vc->vc_x & 31)))
				break;
		}
		return;
	case 10: case 11: case 12:
		lf(vc);
		if (test_bit(CRLF, &vc->flags))
			return;
	case 13:
		cr(vc);
		return;
	case 14:
		return;
	case 15:
		return;
	case 24: case 26:
		return;
	case 27:
		vc->vc_state = ESesc;
		return;
	case 127:
		del(vc);
		return;
	case 128+27:
		vc->vc_state = ESsquare;
		return;
	}
	switch(vc->vc_state) {
	case ESesc:
		vc->vc_state = ESnormal;
		switch (c) {
		case '[':
			vc->vc_state = ESsquare;
			return;
		case ']':
			vc->vc_state = ESnonstd;
			return;
		case '%':
			vc->vc_state = ESpercent;
			return;
		case 'E':
			cr(vc);
			lf(vc);
			return;
		case 'M':
			ri(vc);
			return;
		case 'D':
			lf(vc);
			return;
		case 'H':
			vc->vc_tab_stop[vc->vc_x >> 5] |= (1 << (vc->vc_x & 31));
			return;
		case 'Z':
			respond_ID(tty);
			return;
		case '7':
			save_cur(vc);
			return;
		case '8':
			restore_cur(vc);
			return;
		case '(':
			vc->vc_state = ESsetG0;
			return;
		case ')':
			vc->vc_state = ESsetG1;
			return;
		case '#':
			vc->vc_state = EShash;
			return;
		case 'c':
			reset_terminal(vc, 1);
			return;
		case '>':  /* Numeric keypad */
			return;
		case '=':  /* Application keypad */
			return;
		}
		return;
	case ESnonstd:
		if (c == 'P') {   /* palette escape sequence */
			vc->vc_state = ESpalette;
			return;
		} else if (c=='R') {   /* reset palette */
		} else
			vc->vc_state = ESnormal;
		return;
	case ESpalette:
		vc->vc_state = ESnormal;
		return;
	case ESsquare:
		for (vc->vc_npar = 0; vc->vc_npar < NPAR; vc->vc_npar++)
			vc->vc_par[vc->vc_npar] = 0;
		vc->vc_npar = 0;
		vc->vc_state = ESgetpars;
		if (c == '[') { /* Function key */
			vc->vc_state=ESfunckey;
			return;
		}
		vc->vc_ques = (c == '?');
		if (vc->vc_ques)
			return;
	case ESgetpars:
		if (c == ';' && vc->vc_npar < NPAR - 1) {
			vc->vc_npar++;
			return;
		} else if (c>='0' && c<='9') {
			vc->vc_par[vc->vc_npar] *= 10;
			vc->vc_par[vc->vc_npar] += c - '0';
			return;
		} else
			vc->vc_state = ESgotpars;
	case ESgotpars:
		vc->vc_state = ESnormal;
		switch(c) {
		case 'h':
			set_mode(vc, 1);
			return;
		case 'l':
			set_mode(vc, 0);
			return;
		case 'c':
			break;
		case 'm':
			break;
		case 'n':
			if (!vc->vc_ques) {
				if (vc->vc_par[0] == 5)
					status_report(tty);
				else if (vc->vc_par[0] == 6)
					cursor_report(vc, tty);
			}
			return;
		}
		if (vc->vc_ques) {
			vc->vc_ques = 0;
			return;
		}
		switch(c) {
		case 'G': case '`':
			if (vc->vc_par[0])
				vc->vc_par[0]--;
			gotoxy(vc, vc->vc_par[0], vc->vc_y);
			return;
		case 'A':
			if (!vc->vc_par[0])
				vc->vc_par[0]++;
			gotoxy(vc, vc->vc_x, vc->vc_y - vc->vc_par[0]);
			return;
		case 'B': case 'e':
			if (!vc->vc_par[0])
				vc->vc_par[0]++;
			gotoxy(vc, vc->vc_x, vc->vc_y + vc->vc_par[0]);
			return;
		case 'C': case 'a':
			if (!vc->vc_par[0])
				vc->vc_par[0]++;
			gotoxy(vc, vc->vc_x + vc->vc_par[0], vc->vc_y);
			return;
		case 'D':
			if (!vc->vc_par[0])
				vc->vc_par[0]++;
			gotoxy(vc, vc->vc_x - vc->vc_par[0], vc->vc_y);
			return;
		case 'E':
			if (!vc->vc_par[0])
				vc->vc_par[0]++;
			gotoxy(vc, 0, vc->vc_y + vc->vc_par[0]);
			return;
		case 'F':
			if (!vc->vc_par[0])
				vc->vc_par[0]++;
			gotoxy(vc, 0, vc->vc_y - vc->vc_par[0]);
			return;
		case 'd':
			if (vc->vc_par[0])
				vc->vc_par[0]--;
			gotoxay(vc, vc->vc_x ,vc->vc_par[0]);
			return;
		case 'H': case 'f':
			if (vc->vc_par[0])
				vc->vc_par[0]--;
			if (vc->vc_par[1])
				vc->vc_par[1]--;
			gotoxay(vc, vc->vc_par[1], vc->vc_par[0]);
			return;
		case 'J':
			csi_J(vc, vc->vc_par[0]);
			return;
		case 'K':
			csi_K(vc, vc->vc_par[0]);
			return;
		case 'L':
			csi_L(vc, vc->vc_par[0]);
			return;
		case 'M':
			csi_M(vc, vc->vc_par[0]);
			return;
		case 'P':
			csi_P(vc, vc->vc_par[0]);
			return;
		case 'c':
			if (!vc->vc_par[0])
				respond_ID(tty);
			return;
		case 'g':
			if (!vc->vc_par[0])
				vc->vc_tab_stop[vc->vc_x >> 5] &= ~(1 << (vc->vc_x & 31));
			else if (vc->vc_par[0] == 3) {
				vc->vc_tab_stop[0] =
				vc->vc_tab_stop[1] =
				vc->vc_tab_stop[2] =
				vc->vc_tab_stop[3] =
				vc->vc_tab_stop[4] =
				vc->vc_tab_stop[5] =
				vc->vc_tab_stop[6] =
				vc->vc_tab_stop[7] = 0;
			}
			return;
		case 'm':
			csi_m(vc);
			return;
		case 'q': /* DECLL - but only 3 leds */
			/* map 0,1,2,3 to 0,1,2,4 */
			/*if (vc->vc_par[0] < 4)
				setledstate(kbd_table + vc->vc_num,
					    (vc->vc_par[0] < 3) ? vc->vc_par[0] : 4);*/
			return;
		case 'r':
			if (!vc->vc_par[0])
				vc->vc_par[0]++;
			if (!vc->vc_par[1])
				vc->vc_par[1] = vc->vc_rows;
			/* Minimum allowed region is 2 lines */
			if (vc->vc_par[0] < vc->vc_par[1] &&
			    vc->vc_par[1] <= vc->vc_rows) {
				vc->vc_top = vc->vc_par[0] - 1;
				vc->vc_bottom = vc->vc_par[1];
				gotoxay(vc, 0, 0);
			}
			return;
		case 's':
			save_cur(vc);
			return;
		case 'u':
			restore_cur(vc);
			return;
		case 'X':
			csi_X(vc, vc->vc_par[0]);
			return;
		case '@':
			csi_at(vc, vc->vc_par[0]);
			return;
		case ']': /* setterm functions */
			setterm_command(vc);
			return;
		}
		return;
	case ESpercent:
		vc->vc_state = ESnormal;
		return;
	case ESfunckey:
		vc->vc_state = ESnormal;
		return;
	case EShash:
		vc->vc_state = ESnormal;
		return;
	case ESsetG0:
		vc->vc_state = ESnormal;
		return;
	case ESsetG1:
		vc->vc_state = ESnormal;
		return;
	default:
		vc->vc_state = ESnormal;
	}
}

/* acquires console_lock */
static void __do_con_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
	unsigned char c;
	struct phc_vc_data *vc = tty->driver_data;
	
	PDEBUG_VC("__do_con_write");
	/* no need to caring about the cursor now */
	clear_bit(FLAG_CURSOR_ON, &(phcdev.flags));
	
	while (!tty->stopped && count) {
		c = *buf;
		buf++;
		count--;
		if ((vc->vc_state == ESnormal && c >= 32 && c <= 126)) {
			//printk(" do_con_write  nornmal character %c %d %d \n", c,  vc->vc_y,  vc->vc_x);
			writeDDRAM(charmap[c], vc->vc_y, vc->vc_x);
			vc->vc_need_wrap = 0;
			if (vc->vc_x < vc->vc_cols - 1) {
				vc->vc_x++;
			} else {
				vc->vc_x = 0;
				lf(vc);
			}
			continue;
		}
		//printk("do_con_write specchar %02x %c \n", c, c);
		do_con_trol(tty, vc, c);
	}
	
	/* set the cursor to the proper position */
	set_bit(FLAG_CURSOR_ON, &(phcdev.flags));
	if (!vc->vc_need_wrap) {
		vc->vc_need_wrap = 1;
		set_cursor_pos(vc->vc_y, vc->vc_x);
	}
	
}

